package com.cbarmstrong.MarketWatcher;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.Vector;
import java.util.logging.Logger;

import com.cbarmstrong.Finance.CrossOverIndicator;
import com.cbarmstrong.Finance.Data;
import com.cbarmstrong.Finance.Indicator;
import com.cbarmstrong.Finance.PairCollection;
import com.cbarmstrong.Finance.RangeIndicator;
import com.cbarmstrong.Finance.Ticker;
import com.cbarmstrong.Finance.TradeSet;

public class IndicatorSociety {

	private IndicatorChromosome population[];
	private IndicatorChromosome elite[];
	private Ticker ticker;
	private Logger logger;
	private int stockID;
	private double avgFitness;
	private ArrayList<ArrayList<String>> groups=new ArrayList<ArrayList<String>>();

	public IndicatorSociety() throws SQLException{
		population=new IndicatorChromosome[new Integer(System.getProperty("populationSize","500"))];
		elite=new IndicatorChromosome[new Integer(System.getProperty("populationSize","500"))];
		logger=Data.getLogger();
	}

	public void initialize() throws SQLException{
		Connection c = Data.initConnection();
		Statement s = c.createStatement(), s1=c.createStatement();
		ResultSet r, r1;
		r=s.executeQuery("SELECT DISTINCT groupName FROM indicator_definition");
		while(r.next()){
			r1=s1.executeQuery("SELECT definition FROM indicator_definition WHERE groupName='"+r.getString(1)+"'");
			ArrayList<String> a=new ArrayList<String>();
			while(r1.next()){
				a.add(r1.getString(1));
			}
			groups.add(a);
		}
		int counter=0;
		if(System.getProperties().containsKey("retrieveElite")){
			r = s.executeQuery("SELECT DISTINCT indicators FROM populations WHERE stockid="+stockID);
			while(r.next() && counter<elite.length){
				elite[counter]=new IndicatorChromosome();
				elite[counter].initiate(ticker);
				for(String ind : r.getString(1).split(":")){
					String[] newInd = ind.split(" ");
					if(newInd[0].compareTo("cross")==0){ elite[counter].addCrossIndicator(newInd[1], newInd[2]); logger.finest("Creating cross indicator for "+ind); }
					if(newInd[0].compareTo("range")==0){ elite[counter].addRangeIndicator(newInd[1], newInd[2], newInd[3], new Integer(newInd[4])); logger.finest("Creating range indicator for "+ind); }
				}
				elite[counter].execute();
				//upd.executeUpdate("UPDATE populations SET deviation="+ts.stdDev()+", profit="+ts.getProfit()+", fitness="+population[counter].getFitness()+" WHERE stockid="+stockID+" AND indicators='"+r.getString(1)+"'");
				//if(population[counter].getFitness()!=oldFitness) logger.info("Updating fitness from "+oldFitness+" to "+population[counter].getFitness());
				counter++;
			}
			while(counter<elite.length){
				elite[counter]=getRandomCop();
				elite[counter].initiate(ticker);
				elite[counter++].execute();
			}
		}
		logger.info("Retrieved "+(counter)+" saved processors");
		for(int i=0;i<population.length;i++){
			population[i]=getRandomCop();
		}
		Arrays.sort(elite,new IndicatorComparitor());
		c.close();
	}

	public void run(int generations) throws SQLException{
		logger.info("Running "+generations+" generations on "+ticker.getName()+"("+stockID+")...");
		int pctComplete=0;
		for(int i=0;i<generations;i++){
			executeAll(true);
			if(((double)i*100.0/(double) generations)%10==0){ logger.info(pctComplete+"% Complete - Fitness: "+avgFitness); pctComplete+=10; }
			if (i<generations-1) createNewPopulation();
		}
		Connection c = Data.initConnection();
		Statement s = c.createStatement();
		s.executeUpdate("DELETE FROM populations WHERE stockid="+stockID);
		PreparedStatement p = c.prepareStatement("INSERT INTO populations VALUES ("+stockID+" , ? , ? , ? , ? )");
		TradeSet t;
		for(int i=0;i<elite.length;i++){
			t=elite[i].execute();
			p.setDouble(1, t.stdDev());
			p.setDouble(2, elite[i].getFitness());
			p.setDouble(3, t.getProfit());
			p.setString(4, elite[i].save());
			p.addBatch();
			logger.info("Persisting elite for "+ticker.getName()+" with fitness: "+elite[i].getFitness());
		}
		p.executeBatch();
	}

	public void setTicker(String name, int id) throws SQLException{
		ticker=new Ticker(name, new Integer(System.getProperty("tickerDays","1000")));
		stockID = id;
	}

	private Indicator getRandomIndicator(){
		Indicator retIndicator=null;
		Random random = new Random();
		int g=random.nextInt(groups.size());
		ArrayList<String> group=groups.get(g);
		PairCollection pc = new PairCollection(logger);
		ArrayList<String> indicators=new ArrayList<String>();
		for(String ind : group){
			indicators.addAll(pc.expandDeclaration(ind));
		}
		Random rand = new Random();
		if(rand.nextBoolean()){
			retIndicator = new CrossOverIndicator(indicators.remove(rand.nextInt(indicators.size())), indicators.remove(rand.nextInt(indicators.size())), ticker);
		} else{
			retIndicator = new RangeIndicator(indicators.remove(rand.nextInt(indicators.size())), indicators.remove(rand.nextInt(indicators.size())), indicators.remove(rand.nextInt(indicators.size())), ticker, rand.nextInt(3));
		}
		//logger.fine("Created random indicator "+retIndicator.save());
		return retIndicator;
	}

	private IndicatorChromosome getRandomCop() throws SQLException {
		IndicatorChromosome cop=new IndicatorChromosome();
		for(int i=0;i<new Integer(System.getProperty("maxDepth","4"));i++){
			if(i>0 && 100*Math.random()<new Double(System.getProperty("chanceOfNull", "5"))){ continue; }
			Indicator ind=getRandomIndicator();
			cop.addIndicator(ind);
		}
		return cop;
	}

	private void executeAll(boolean save) throws SQLException{
		//Connection c = Data.initConnection();
		//Statement s = c.createStatement();
		//ResultSet counter=s.executeQuery("SELECT COUNT(*) from populations where stockid="+stockID);
		//counter.first();
		//int count = counter.getInt(1);
		//PreparedStatement removeWeakest = c.prepareStatement("DELETE FROM populations WHERE stockid="+stockID+" ORDER BY fitness ASC LIMIT 1");
		//PreparedStatement min = c.prepareStatement("SELECT MIN(fitness) from populations where stockid="+stockID);
		//PreparedStatement dup = c.prepareStatement("SELECT * FROM populations where stockid="+stockID+" and indicators=?");
		//PreparedStatement p = c.prepareStatement("INSERT INTO populations VALUES ( "+stockID+" , ? , ? , ? , ? )");
		avgFitness=0;
		for(int i=0;i<population.length;i++){
			population[i].initiate(ticker);
			TradeSet ts=population[i].execute();
			avgFitness+=population[i].getFitness();
			boolean goodEnough=ts.stdDevPct()>0 && ts.getProfit()>0;
			if(save && goodEnough){
				double minFitness=elite[elite.length-1].getFitness();
				if(minFitness<population[i].getFitness()){	
					//logger.fine("Performing update as min fitness is "+minFitness+" and latest fitness is "+population[i].getFitness());
					boolean duplicate=false;
					for(int j=0; j<elite.length; j++){
						if(elite[j].save().compareTo(population[i].save())==0){ duplicate=true ; } 
					}
					if(!duplicate){
						logger.info("Inserting stronger system: "+population[i].save());
						logger.info("Fitness: "+population[i].getFitness()+", Profit: "+ts.getProfit()+", Dev: "+ts.stdDev()+", Trades: "+ts.tradeCount());
						elite[elite.length-1]=new IndicatorChromosome();
						for(Indicator in : population[i].getIndicators()){ elite[elite.length-1].addIndicator(in); }
						elite[elite.length-1].initiate(ticker);
						elite[elite.length-1].execute();
						Arrays.sort(elite, new IndicatorComparitor());
						logger.fine(elite[0].getFitness()+" -> "+elite[elite.length-1].getFitness());
					}
				}
			}
		}
		avgFitness/=population.length;
		Arrays.sort(population,new IndicatorComparitor());
		//p.close();
		//c.close();
		//population[0].initiate(ticker);
		//TradeSet best=population[0].execute();
		//logger.fine("Average fitness: "+ avgFitness+" - Best: "+population[0].getFitness() +" - Worst: "+population[population.length-1].getFitness());
		//logger.info("Best result: "+best.getProfit()+" points over "+best.closedTrades()+" trades with deviation "+best.stdDevPct()+"%");
		//logger.info("Using: "+Arrays.toString(population[0].getIndicators()));
	}

	private void createNewPopulation(){
		IndicatorChromosome[] newPopulation=new IndicatorChromosome[population.length];
		//logger.fine("Creating new population");
		for(int i=0;i<population.length;i++){
			if(i<new Integer(System.getProperty("propagateElite", "1"))){ newPopulation[i]=population[i]; }
			else{ 
				newPopulation[i]=createChild(getTournamentWinner(), getTournamentWinner());
				if(newPopulation[i].getIndicators().size()==0){ i--; }
			}
		}
		population=newPopulation;
		//logger.finest("New population: "+Arrays.toString(population));
	}

	private IndicatorChromosome createChild(IndicatorChromosome mother, IndicatorChromosome father) {
		IndicatorChromosome child=new IndicatorChromosome();
		Vector<Indicator> fatherIndicators = father.getIndicators();
		Vector<Indicator> motherIndicators = mother.getIndicators();
		for(int i=0;i<fatherIndicators.size();i++){
			if(Math.random()*100<new Double(System.getProperty("mutationChance", "10"))){ 
				//logger.finer("Mutation occured on chromosome "+i);
				Indicator ind=getRandomIndicator();
				child.addIndicator(ind);
			}
			else if(Math.random()>0.5){ child.addIndicator(fatherIndicators.get(i)); }
			else { if(i<motherIndicators.size()) child.addIndicator(motherIndicators.get(i)); }
		}
		return child;
	}

	private IndicatorChromosome getTournamentWinner() {
		IndicatorChromosome[] entries=new IndicatorChromosome[(int) (population.length*0.2)];
		for(int i=0;i<entries.length;i++){
			entries[i]=population[(int) (Math.random()*population.length)];
		}
		Arrays.sort(entries, new IndicatorComparitor());
		return entries[0];
	}

}
